﻿using System;
using System.Text;
using Adaptive.Agrona;

#pragma warning disable CS8618
namespace Unity.Type.BinaryType
{
    public sealed class SubStatus
    {
        public const int BlockLength = 316;
        public const int TemplateId = 4;
        public const int SchemaId = 1;
        public const int SchemaVersion = 0;
        public const string SemanticType = "";

        public const ByteOrder DefaultByteOrder = ByteOrder.LittleEndian;

        private readonly SubStatus _parentMessage;
        private IMutableDirectBuffer _buffer;
        private int _offset;
        private int _limit;
        private int _actingBlockLength;
        private int _actingVersion;

        public int Offset => _offset;

        public SubStatus()
        {
            _parentMessage = this;
        }

        public void WrapForEncode(IMutableDirectBuffer buffer, int offset)
        {
            _buffer = buffer;
            _offset = offset;
            _actingBlockLength = BlockLength;
            _actingVersion = SchemaVersion;
            Limit = offset + _actingBlockLength;
        }

        public void WrapForEncodeAndApplyHeader(IMutableDirectBuffer buffer, int offset, MessageHeader headerEncoder)
        {
            headerEncoder.Wrap(buffer, offset, SchemaVersion);
            headerEncoder.BlockLength = BlockLength;
            headerEncoder.TemplateId = TemplateId;
            headerEncoder.SchemaId = SchemaId;
            headerEncoder.Version = SchemaVersion;

            WrapForEncode(buffer, offset + MessageHeader.Size);
        }

        public void WrapForDecode(IMutableDirectBuffer buffer, int offset, int actingBlockLength, int actingVersion)
        {
            _buffer = buffer;
            _offset = offset;
            _actingBlockLength = actingBlockLength;
            _actingVersion = actingVersion;
            Limit = offset + _actingBlockLength;
        }

        public int Size => _limit - _offset;

        public int Limit
        {
            get => _limit;
            set
            {
                _buffer.CheckLimit(value);
                _limit = value;
            }
        }


        public const int ChangerCsiIdId = 1;
        public const int ChangerCsiIdSinceVersion = 0;
        public const int ChangerCsiIdDeprecated = 0;
        public bool ChangerCsiIdInActingVersion()
        {
            return _actingVersion >= ChangerCsiIdSinceVersion;
        }

        public const int ChangerCsiIdOffset = 0;

        public static string ChangerCsiIdMetaAttribute(MetaAttribute metaAttribute)
        {
            switch (metaAttribute)
            {
                case MetaAttribute.Epoch:
                case MetaAttribute.TimeUnit:
                case MetaAttribute.SemanticType:
                    return "";
                case MetaAttribute.Presence:
                    return "required";
                default:
                    return "";
            }
        }

        public const int ChangerCsiIdNullValue = -2147483648;
        public const int ChangerCsiIdMinValue = -2147483647;
        public const int ChangerCsiIdMaxValue = 2147483647;

        public int ChangerCsiId
        {
            get => _buffer.GetInt(_offset + 0, DefaultByteOrder);
            set => _buffer.PutInt(_offset + 0, value, DefaultByteOrder);
        }


        public const int FetchedIdsId = 2;
        public const int FetchedIdsSinceVersion = 0;
        public const int FetchedIdsDeprecated = 0;
        public bool FetchedIdsInActingVersion()
        {
            return _actingVersion >= FetchedIdsSinceVersion;
        }

        public const int FetchedIdsOffset = 4;

        public static string FetchedIdsMetaAttribute(MetaAttribute metaAttribute)
        {
            switch (metaAttribute)
            {
                case MetaAttribute.Epoch:
                case MetaAttribute.TimeUnit:
                case MetaAttribute.SemanticType:
                    return "";
                case MetaAttribute.Presence:
                    return "required";
                default:
                    return "";
            }
        }

        public const long FetchedIdsNullValue = -9223372036854775808L;
        public const long FetchedIdsMinValue = -9223372036854775807L;
        public const long FetchedIdsMaxValue = 9223372036854775807L;

        public long FetchedIds
        {
            get => _buffer.GetLong(_offset + 4, DefaultByteOrder);
            set => _buffer.PutLong(_offset + 4, value, DefaultByteOrder);
        }


        public const int FingerprintId = 3;
        public const int FingerprintSinceVersion = 0;
        public const int FingerprintDeprecated = 0;
        public bool FingerprintInActingVersion()
        {
            return _actingVersion >= FingerprintSinceVersion;
        }

        public const int FingerprintOffset = 12;

        public static string FingerprintMetaAttribute(MetaAttribute metaAttribute)
        {
            switch (metaAttribute)
            {
                case MetaAttribute.Epoch:
                case MetaAttribute.TimeUnit:
                case MetaAttribute.SemanticType:
                    return "";
                case MetaAttribute.Presence:
                    return "required";
                default:
                    return "";
            }
        }

        public const byte FingerprintNullValue = 0;
        public const byte FingerprintMinValue = 32;
        public const byte FingerprintMaxValue = 126;

        public const int FingerprintLength = 256;

        public byte GetFingerprint(int index)
        {
            if ((uint)index >= 256)
            {
                MetadataExceptionHelper.ThrowIndexOutOfRangeException(index);
            }

            return _buffer.GetByte(_offset + 12 + (index * 1));
        }

        public void SetFingerprint(int index, byte value)
        {
            if ((uint)index >= 256)
            {
                MetadataExceptionHelper.ThrowIndexOutOfRangeException(index);
            }

            _buffer.PutByte(_offset + 12 + (index * 1), value);
        }

        public ReadOnlySpan<byte> Fingerprint
        {
            get => FingerprintAsSpan();
            set => value.CopyTo(_buffer.ByteArray.AsSpan(_offset + 12, FingerprintLength));
        }

        public Span<byte> FingerprintAsSpan()
        {
            byte[] fingerprintData = new byte[FingerprintLength];
            _buffer.GetBytes(_offset + 12, fingerprintData, 0, FingerprintLength);
            return new Span<byte>(fingerprintData);
        }

        public const string FingerprintCharacterEncoding = "ASCII";
        public static Encoding FingerprintResolvedCharacterEncoding = Encoding.GetEncoding(FingerprintCharacterEncoding);


        public int GetFingerprint(byte[] dst, int dstOffset)
        {
            const int length = 256;
            return GetFingerprint(new Span<byte>(dst, dstOffset, length));
        }

        public int GetFingerprint(Span<byte> fingerprintData)
        {
            const int length = 256;
            if (fingerprintData.Length < length)
            {
                MetadataExceptionHelper.ThrowWhenSpanLengthTooSmall(fingerprintData.Length);
            }

            _buffer.GetBytes(_offset + 12, fingerprintData.ToArray());
            return length;
        }

        public void SetFingerprint(byte[] src, int srcOffset)
        {
            SetFingerprint(new ReadOnlySpan<byte>(src, srcOffset, src.Length - srcOffset));
        }

        public void SetFingerprint(ReadOnlySpan<byte> fingerprintData)
        {
            const int length = 256;
            if (fingerprintData.Length > length)
            {
                MetadataExceptionHelper.ThrowWhenSpanLengthTooLarge(fingerprintData.Length);
            }

            _buffer.PutBytes(_offset + 12, fingerprintData.ToArray());
        }

        public void SetFingerprint(string value)
        {
            MetadataHelper.SetNullTerminatedBytesFromString(_buffer, value, _offset + 12, FingerprintLength, FingerprintNullValue);
        }
        public string GetFingerprint()
        {
            return MetadataHelper.GetStringFromNullTerminatedBytes(_buffer, _offset + 12, FingerprintLength, FingerprintNullValue);
        }

        public const int IdentId = 4;
        public const int IdentSinceVersion = 0;
        public const int IdentDeprecated = 0;
        public bool IdentInActingVersion()
        {
            return _actingVersion >= IdentSinceVersion;
        }

        public const int IdentOffset = 268;

        public static string IdentMetaAttribute(MetaAttribute metaAttribute)
        {
            switch (metaAttribute)
            {
                case MetaAttribute.Epoch:
                case MetaAttribute.TimeUnit:
                case MetaAttribute.SemanticType:
                    return "";
                case MetaAttribute.Presence:
                    return "required";
                default:
                    return "";
            }
        }

        public const byte IdentNullValue = 0;
        public const byte IdentMinValue = 32;
        public const byte IdentMaxValue = 126;

        public const int IdentLength = 32;

        public byte GetIdent(int index)
        {
            if ((uint)index >= 32)
            {
                MetadataExceptionHelper.ThrowIndexOutOfRangeException(index);
            }

            return _buffer.GetByte(_offset + 268 + (index * 1));
        }

        public void SetIdent(int index, byte value)
        {
            if ((uint)index >= 32)
            {
                MetadataExceptionHelper.ThrowIndexOutOfRangeException(index);
            }

            _buffer.PutByte(_offset + 268 + (index * 1), value);
        }

        public ReadOnlySpan<byte> Ident
        {
            get => IdentAsSpan();
            set => value.CopyTo(_buffer.ByteArray.AsSpan(_offset + 268, IdentLength));
        }

        public Span<byte> IdentAsSpan()
        {
            byte[] identData = new byte[IdentLength];
            _buffer.GetBytes(_offset + 268, identData, 0, IdentLength);
            return new Span<byte>(identData);
        }

        public const string IdentCharacterEncoding = "ASCII";
        public static Encoding IdentResolvedCharacterEncoding = Encoding.GetEncoding(IdentCharacterEncoding);


        public int GetIdent(byte[] dst, int dstOffset)
        {
            const int length = 32;
            return GetIdent(new Span<byte>(dst, dstOffset, length));
        }

        public int GetIdent(Span<byte> identData)
        {
            const int length = 32;
            if (identData.Length < length)
            {
                MetadataExceptionHelper.ThrowWhenSpanLengthTooSmall(identData.Length);
            }

            _buffer.GetBytes(_offset + 268, identData.ToArray());
            return length;
        }

        public void SetIdent(byte[] src, int srcOffset)
        {
            SetIdent(new ReadOnlySpan<byte>(src, srcOffset, src.Length - srcOffset));
        }

        public void SetIdent(ReadOnlySpan<byte> identData)
        {
            const int length = 32;
            if (identData.Length > length)
            {
                MetadataExceptionHelper.ThrowWhenSpanLengthTooLarge(identData.Length);
            }

            _buffer.PutBytes(_offset + 268, identData.ToArray());
        }

        public void SetIdent(string value)
        {
            MetadataHelper.SetNullTerminatedBytesFromString(_buffer, value, _offset + 268, IdentLength, IdentNullValue);
        }
        public string GetIdent()
        {
            return MetadataHelper.GetStringFromNullTerminatedBytes(_buffer, _offset + 268, IdentLength, IdentNullValue);
        }

        public const int VersionId = 5;
        public const int VersionSinceVersion = 0;
        public const int VersionDeprecated = 0;
        public bool VersionInActingVersion()
        {
            return _actingVersion >= VersionSinceVersion;
        }

        public const int VersionOffset = 300;

        public static string VersionMetaAttribute(MetaAttribute metaAttribute)
        {
            switch (metaAttribute)
            {
                case MetaAttribute.Epoch:
                case MetaAttribute.TimeUnit:
                case MetaAttribute.SemanticType:
                    return "";
                case MetaAttribute.Presence:
                    return "required";
                default:
                    return "";
            }
        }

        public const byte VersionNullValue = 0;
        public const byte VersionMinValue = 32;
        public const byte VersionMaxValue = 126;

        public const int VersionLength = 16;

        public byte GetVersion(int index)
        {
            if ((uint)index >= 16)
            {
                MetadataExceptionHelper.ThrowIndexOutOfRangeException(index);
            }

            return _buffer.GetByte(_offset + 300 + (index * 1));
        }

        public void SetVersion(int index, byte value)
        {
            if ((uint)index >= 16)
            {
                MetadataExceptionHelper.ThrowIndexOutOfRangeException(index);
            }

            _buffer.PutByte(_offset + 300 + (index * 1), value);
        }

        public ReadOnlySpan<byte> Version
        {
            get => VersionAsSpan();
            set => value.CopyTo(_buffer.ByteArray.AsSpan(_offset + 300, VersionLength));
        }

        public Span<byte> VersionAsSpan()
        {
            byte[] versionData = new byte[VersionLength];
            _buffer.GetBytes(_offset + 300, versionData, 0, VersionLength);
            return new Span<byte>(versionData);
        }

        public const string VersionCharacterEncoding = "ASCII";
        public static Encoding VersionResolvedCharacterEncoding = Encoding.GetEncoding(VersionCharacterEncoding);


        public int GetVersion(byte[] dst, int dstOffset)
        {
            const int length = 16;
            return GetVersion(new Span<byte>(dst, dstOffset, length));
        }

        public int GetVersion(Span<byte> versionData)
        {
            const int length = 16;
            if (versionData.Length < length)
            {
                MetadataExceptionHelper.ThrowWhenSpanLengthTooSmall(versionData.Length);
            }

            _buffer.GetBytes(_offset + 300, versionData.ToArray());
            return length;
        }

        public void SetVersion(byte[] src, int srcOffset)
        {
            SetVersion(new ReadOnlySpan<byte>(src, srcOffset, src.Length - srcOffset));
        }

        public void SetVersion(ReadOnlySpan<byte> versionData)
        {
            const int length = 16;
            if (versionData.Length > length)
            {
                MetadataExceptionHelper.ThrowWhenSpanLengthTooLarge(versionData.Length);
            }

            _buffer.PutBytes(_offset + 300, versionData.ToArray());
        }

        public void SetVersion(string value)
        {
            MetadataHelper.SetNullTerminatedBytesFromString(_buffer, value, _offset + 300, VersionLength, VersionNullValue);
        }
        public string GetVersion()
        {
            return MetadataHelper.GetStringFromNullTerminatedBytes(_buffer, _offset + 300, VersionLength, VersionNullValue);
        }

        public override string ToString()
        {
            var sb = new StringBuilder(100);
            BuildString(sb);
            return sb.ToString();
        }

        internal void BuildString(StringBuilder builder)
        {
            if (_buffer == null)
            {
                throw new InvalidOperationException("Can't provide string value of null buffer");
            }

            int originalLimit = Limit;
            Limit = _offset + _actingBlockLength;
            builder.Append("[SubStatus](sbeTemplateId=");
            builder.Append(TemplateId);
            builder.Append("|sbeSchemaId=");
            builder.Append(SchemaId);
            builder.Append("|sbeSchemaVersion=");
            if (_parentMessage._actingVersion != SchemaVersion)
            {
                builder.Append(_parentMessage._actingVersion);
                builder.Append('/');
            }
            builder.Append(SchemaVersion);
            builder.Append("|sbeBlockLength=");
            if (_actingBlockLength != BlockLength)
            {
                builder.Append(_actingBlockLength);
                builder.Append('/');
            }
            builder.Append(BlockLength);
            builder.Append("):");

            builder.Append("ChangerCsiId=");
            builder.Append(ChangerCsiId);
            builder.Append('|');
            builder.Append("FetchedIds=");
            builder.Append(FetchedIds);
            builder.Append('|');
            builder.Append("Fingerprint=");
            for (int i = 0; i < FingerprintLength && GetFingerprint(i) > 0; ++i)
            {
                builder.Append((char)GetFingerprint(i));
            }
            builder.Append('|');
            builder.Append("Ident=");
            for (int i = 0; i < IdentLength && GetIdent(i) > 0; ++i)
            {
                builder.Append((char)GetIdent(i));
            }
            builder.Append('|');
            builder.Append("Version=");
            for (int i = 0; i < VersionLength && GetVersion(i) > 0; ++i)
            {
                builder.Append((char)GetVersion(i));
            }

            Limit = originalLimit;

        }
    }
}
